package com.eascs.ticket.ticket.service;

import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import javax.inject.Inject;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.eascs.app.dao.PageResult;
import com.eascs.app.dao.SqlPageQuery;
import com.eascs.app.log.Logger;
import com.eascs.app.log.LoggerFactory;
import com.eascs.common.assertion.util.AssertInfoUtils;
import com.eascs.common.util.DateUtils;
import com.eascs.common.util.StringUtil;
import com.eascs.ticket.common.component.MailNotifierComponent;
import com.eascs.ticket.common.entity.EntityUtil;
import com.eascs.ticket.common.entity.StatusConstant;
import com.eascs.ticket.common.service.BaseSevice;
import com.eascs.ticket.configuration.entity.DictionaryDataConstant;
import com.eascs.ticket.configuration.entity.TicketStatus;
import com.eascs.ticket.configuration.entity.TicketStatusConstant;
import com.eascs.ticket.configuration.entity.TicketTypeConstant;
import com.eascs.ticket.configuration.entity.TicketTypeStatus;
import com.eascs.ticket.configuration.entity.UserAutoAssign;
import com.eascs.ticket.configuration.service.DictionarySeqService;
import com.eascs.ticket.configuration.service.TicketStatusService;
import com.eascs.ticket.configuration.service.TicketTypeStatusService;
import com.eascs.ticket.configuration.service.UserAutoAssignService;
import com.eascs.ticket.ticket.date.TicketDueDate;
import com.eascs.ticket.ticket.date.TicketDueDateHelper;
import com.eascs.ticket.ticket.entity.Ticket;
import com.eascs.ticket.ticket.entity.TicketComment;
import com.eascs.ticket.ticket.entity.TicketDescriptionsUpdate;
import com.eascs.ticket.ticket.entity.TicketDueDateUpdate;
import com.eascs.ticket.ticket.entity.TicketOperatorUpdate;
import com.eascs.ticket.ticket.entity.TicketPlanUpdate;
import com.eascs.ticket.ticket.entity.TicketSatusChange;
import com.eascs.ticket.ticket.entity.TicketUpdate;
import com.eascs.ticket.ticket.entity.TicketWatcher;
import com.eascs.ticket.ticket.entity.VersionTicketIdUpdate;
import com.eascs.ticket.ticket.vo.TicketChangeStatusVo;
import com.eascs.ticket.ticket.vo.TicketUpdateOperatorVo;
import com.eascs.ticket.ticket.vo.WFEmailContentVO;
import com.eascs.ticket.user.component.SystemUserHolder;
import com.eascs.ticket.user.entity.User;
import com.eascs.ticket.user.service.UserService;
import com.eascs.web.sso.UserHolder;

import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;

@Service
public class TicketService extends BaseSevice {
	private static Logger log = LoggerFactory.getLogger(TicketService.class);
	/** 工单详情URL */
	private static final String GDDETAILURL = "http://tick.o2ojr.eascs.com/ticket/view/%s";
	@Inject
	private TicketChangeLogService ticketChangeLogService;

	@Inject
	private DictionarySeqService dictionarySeqService;

	@Inject
	private TicketTypeStatusService ticketTypeStatusService;

	@Inject
	private UserService userService;

	@Inject
	private TicketWatcherService ticketWatcherService;

	@Inject
	private TicketStatusChangeLogService ticketStatusChangeLogService;

	@Inject
	private TicketStatusTimeLogService ticketStatusTimeLogService;

	@Inject
	private TicketStatusService ticketStatusService;

	@Inject
	private SystemUserHolder systemUserHolder;

	@Inject
	private UserAutoAssignService userAutoAssignService;

	@Inject
	private MailNotifierComponent mailNotifierComponent;

	private static final Pattern VERSION_TITLE = Pattern.compile("^201[6-9][0-9]{4}[a-z]?$");

	@Transactional(transactionManager = "ticketTransactionManager")
	public Ticket insertTicket(Ticket ticket) {
		AssertInfoUtils.assertTrue(!TicketTypeConstant.TICKET_TYPE_CODE_VERSION.equals(ticket.getTicketTypeCode()) || VERSION_TITLE.matcher(ticket.getTitle()).matches(), "版本的标题格式必须为20160101或者20160101a的格式");
		String ticketId = dictionarySeqService.buildSeq(ticket.getProjectId(), ticket.getProjectId() + "-");
		ticket.setId(ticketId);
		this.proxyDao.insert(ticket);
		this.updateTicketDueDate(ticket.getId());

		TicketWatcher ticketWatcher = new TicketWatcher();
		// 如果是系统创建的单子
		if ("system".equals(ticket.getCreator())) {
			ticketWatcher.setCreateDate(new Date());
			ticketWatcher.setCreator(ticket.getCreator());
			ticketWatcher.setUpdateDate(new Date());
			ticketWatcher.setUpdator(ticket.getCreator());
		} else {
			EntityUtil.initInsertEntity(ticketWatcher);
		}
		ticketWatcher.setTicketId(ticket.getId());
		ticketWatcher.setWatcherUserCode(ticket.getCreator());
		ticketWatcher.setStatus(StatusConstant.STATUS_OK);

		ticketWatcherService.insertTicketWatcher(ticketWatcher);

		ticketChangeLogService.insertTicketChangeLogWithObject(ticket.getId(), "创建工单", ticket);
		ticketStatusChangeLogService.insertTicketStatusChangeLog(ticket.getId(), ticket.getOperator(), ticket.getStatus(), ticket.getStatus());
		ticketStatusTimeLogService.insertStartTimeLog(ticket.getId(), ticket.getStatus(), ticket.getOperator(), ticket.getCreateDate());

		this.autoAssign(ticketId);
		return ticket;
	}

	@Transactional(transactionManager = "ticketTransactionManager")
	public void updateTickect(TicketUpdate ticketUpdate) {
		checkCanUpdate(ticketUpdate.getId());
		Ticket ticketDb = this.getTicketById(ticketUpdate.getId());
		AssertInfoUtils.assertTrue(!TicketTypeConstant.TICKET_TYPE_CODE_VERSION.equals(ticketDb.getTicketTypeCode()) || VERSION_TITLE.matcher(ticketUpdate.getTitle()).matches(), "版本的标题格式必须为20160101或者20160101a的格式");

		com.eascs.web.sso.User user = UserHolder.getUser();
		AssertInfoUtils.assertTrue(user.isSuperAdministrator() || user.getCode().equals(ticketDb.getCreator()), "只有创建人或者管理员才有修改工单的仅限");

		this.proxyDao.updateById(ticketUpdate);
		this.updateTicketDueDate(ticketUpdate.getId());

		// update version ticket due date
		if (TicketTypeConstant.TICKET_TYPE_CODE_VERSION.equals(ticketDb.getTicketTypeCode())) {
			List<String> ids = this.getTicketIdsWithVersionTicketId(ticketDb.getId());
			for (String ticketId : ids) {
				this.updateTicketDueDate(ticketId);
			}
		}

		ticketChangeLogService.insertTicketChangeLogWithObject(ticketUpdate.getId(), "修改工单基本信息", ticketUpdate);
	}

	@Transactional(transactionManager = "ticketTransactionManager")
	public void updateTickectPlan(TicketPlanUpdate ticketPlanUpdate) {
		checkCanUpdate(ticketPlanUpdate.getId());
		Ticket ticketDb = this.getTicketById(ticketPlanUpdate.getId());
		// 同步父工单的版本到本工单
		if (StringUtil.hasText(ticketDb.getParentTicketId())) {
			Ticket parentTicket = this.getTicketById(ticketDb.getParentTicketId());
			ticketPlanUpdate.setVersionTicketId(parentTicket.getVersionTicketId());
		}

		// 同步本工单下所有子工单的版本信息
		List<Object[]> childTickeList = getChildTicketList(ticketDb.getId());
		for (Object[] objects : childTickeList) {
			VersionTicketIdUpdate versionTicketIdUpdate = new VersionTicketIdUpdate();
			versionTicketIdUpdate.setId(objects[0].toString());
			versionTicketIdUpdate.setVersionTicketId(ticketPlanUpdate.getVersionTicketId());
			EntityUtil.initUpdateEntity(versionTicketIdUpdate);
			this.proxyDao.updateById(versionTicketIdUpdate);
			ticketChangeLogService.insertTicketChangeLogWithObject(versionTicketIdUpdate.getId(), "同步子工单版本", versionTicketIdUpdate);
		}

		this.proxyDao.updateById(ticketPlanUpdate);
		TicketDueDateUpdate ticketDueDateUpdate = this.updateTicketDueDate(ticketPlanUpdate.getId());

		checkUserAndStartus(ticketPlanUpdate, ticketDb, ticketDueDateUpdate);

		// update version ticket due date
		if (TicketTypeConstant.TICKET_TYPE_CODE_VERSION.equals(ticketDb.getTicketTypeCode())) {
			List<String> ids = this.getTicketIdsWithVersionTicketId(ticketDb.getId());
			for (String ticketId : ids) {
				this.updateTicketDueDate(ticketId);
			}
		}

		ticketChangeLogService.insertTicketChangeLogWithObject(ticketPlanUpdate.getId(), "调整工单计划", ticketPlanUpdate);
		if (StringUtil.hasText(ticketDb.getVersionTicketId()) && !ticketDb.getVersionTicketId().equals(ticketPlanUpdate.getVersionTicketId())) {
			ticketChangeLogService.insertTicketChangeLogWithObject(ticketDb.getVersionTicketId(), "移除版本关联工单", ticketPlanUpdate);
		}
		if (StringUtil.hasText(ticketPlanUpdate.getVersionTicketId()) && !ticketPlanUpdate.getVersionTicketId().equals(ticketDb.getVersionTicketId())) {
			ticketChangeLogService.insertTicketChangeLogWithObject(ticketPlanUpdate.getVersionTicketId(), "增加版本关联工单", ticketPlanUpdate);
		}
	}

	private void checkUserAndStartus(TicketPlanUpdate ticketPlanUpdate, Ticket ticketDb, TicketDueDateUpdate ticketDueDateUpdate) {
		com.eascs.web.sso.User user = UserHolder.getUser();
		if (user.isSuperAdministrator())
			return;
		// AssertInfoUtils.assertTrue(TicketStatusConstant.TICKET_STATUS_TYPE_PM.equals(this.ticketStatusService.getTicketStatusById(ticketDb.getStatus()).getTicketStatusType()),
		// "实施中工单的计划只有管理员才有仅限调整计划");
		// 功能和改进类型的工单计划只能提前，如果需要延后处理，请向管理员申请
		if (TicketTypeConstant.TICKET_TYPE_CODE_FEATURE.equals(ticketDb.getTicketTypeCode()) || TicketTypeConstant.TICKET_TYPE_CODE_OPTIMIZE.equals(ticketDb.getTicketTypeCode())) {
			AssertInfoUtils.assertTrue(ticketDb.getRequirementReviewedTime().compareTo(ticketPlanUpdate.getRequirementReviewedTime()) >= 0, "功能和改进类型的计划需求评审完成时间只能提前，如果需要延后处理，请向管理员申请");
			AssertInfoUtils.assertTrue(ticketDb.getImplementationReviewTime().compareTo(ticketPlanUpdate.getImplementationReviewTime()) >= 0, "功能和改进类型的计划提测时间只能提前，如果需要延后处理，请向管理员申请");
		}
		AssertInfoUtils.assertTrue(ticketDb.getDueDate().compareTo(ticketDueDateUpdate.getDueDate()) >= 0, "计划只能提前，如果需要延后处理，请向管理员申请");
	}

	@Transactional(transactionManager = "ticketTransactionManager")
	public void updateTicketDescriptions(TicketDescriptionsUpdate ticketDescriptionsUpdate) {
		checkCanUpdate(ticketDescriptionsUpdate.getId());
		this.proxyDao.updateById(ticketDescriptionsUpdate);
		ticketChangeLogService.insertTicketChangeLogWithObject(ticketDescriptionsUpdate.getId(), "修改描述", ticketDescriptionsUpdate);
	}

	/*
	 * 判断是否能修改，已经取消或者关注的工单是不能修改的，除非是管理员
	 */
	private void checkCanUpdate(String ticketId) {
		com.eascs.web.sso.User user = UserHolder.getUser();
		if (user.isSuperAdministrator())
			return;
		Ticket ticket = this.getTicketById(ticketId);
		AssertInfoUtils.assertTrue(!TicketStatusConstant.TICKET_STATUS_CLOSE.equals(ticket.getStatus()) && !TicketStatusConstant.TICKET_STATUS_CANCEL.equals(ticket.getStatus()), "已经取消或者关注的工单是不能修改的!");
	}

	public Ticket getTicketById(String ticketId) {
		return this.proxyDao.getById(Ticket.class, ticketId);
	}

	public PageResult<Object[]> findTicketDataWithPage(SqlPageQuery<Object[]> sqlPageQuery) {
		return this.proxyDao.findDataWithPage(sqlPageQuery);
	}

	public List<Object[]> getChildTicketList(String ticketId) {
		return this.proxyDao.findObjectsList("select id,title from ticket where parent_ticket_id=?", ticketId);
	}

	public List<String> getTicketIdsWithVersionTicketId(String versionTicketId) {
		return this.proxyDao.findSimpleObjectList("select id from ticket where version_ticket_id=?", String.class, versionTicketId);
	}

	@Transactional(transactionManager = "ticketTransactionManager")
	public void changeStatus(TicketChangeStatusVo ticketChangeStatusVo) {
		Ticket ticket = this.getTicketById(ticketChangeStatusVo.getTicketId());
		com.eascs.web.sso.User user = UserHolder.getUser();
		AssertInfoUtils.assertTrue(user.isSuperAdministrator() || ticket.getOperator().equals(user.getCode()), "当前用户以及管理员才有权限操作");

		TicketTypeStatus ticketTypeStatus = ticketTypeStatusService.getTicketTypeStatusById(ticketChangeStatusVo.getTicketTypeStatusId());
		AssertInfoUtils.assertNotNull(ticketTypeStatus, "当前工单状态已经变化，无法执行该操作");
		AssertInfoUtils.assertTrue(ticketTypeStatus.getCurrentStatus().equals(ticket.getStatus()), "当前工单状态已经变化，无法执行该操作");
		AssertInfoUtils.assertTrue(!TicketStatusConstant.TICKET_STATUS_CANCEL.equals(ticketTypeStatus.getNextStatus()) || user.isSuperAdministrator(), "只有管理员才有权限取消该工单，请转给管理员复核后取消！");
		TicketSatusChange ticketSatusChange = new TicketSatusChange();
		EntityUtil.initUpdateEntity(ticketSatusChange);
		ticketSatusChange.setId(ticket.getId());
		ticketSatusChange.setStatus(ticketTypeStatus.getNextStatus());

		// 用户需求工单关闭工单（用户需求处理完成），修改userdenamd表的status状态字段为CLO
		if ("CLOSE".equals(ticketTypeStatus.getNextStatus()) && "pdr".equals(ticket.getProjectId())) {
			String sql = "update user_demand f set f.status='CLO' where f.bill_code=?";
			this.o2oproxyDao.execute(sql, ticket.getId());
		}
		this.proxyDao.updateById(ticketSatusChange);
		this.updateTicketDueDate(ticket.getId());

		// insert comments
		insertComments(ticketChangeStatusVo.getTicketId(), ticketChangeStatusVo.getComments());

		changeOperatorWhenChangeStatus(ticket, user, ticketTypeStatus, ticketChangeStatusVo.getOperator());

		ticketChangeLogService.insertTicketChangeLogWithObject(ticketChangeStatusVo.getTicketId(), "修改状态:" + ticketTypeStatus.getName(), ticketChangeStatusVo);

		Ticket newTicket = this.getTicketById(ticketChangeStatusVo.getTicketId());
		ticketStatusChangeLogService.insertTicketStatusChangeLog(newTicket.getId(), newTicket.getOperator(), ticketTypeStatus.getCurrentStatus(), ticketTypeStatus.getNextStatus());
		ticketStatusTimeLogService.updateEndTimeLog(ticket.getId(), ticket.getStatus(), ticket.getOperator(), new Date());
		ticketStatusTimeLogService.insertStartTimeLog(newTicket.getId(), newTicket.getStatus(), newTicket.getOperator(), new Date());

		this.autoAssign(ticketChangeStatusVo.getTicketId());
	}

	/**
	 * @author 唐洪全注释 这一段是更改状态的时候，修改经办人
	 * @param ticket
	 * @param user
	 * @param ticketTypeStatus
	 * @param operator
	 */
	@Transactional(transactionManager = "ticketTransactionManager")
	private void changeOperatorWhenChangeStatus(Ticket ticket, com.eascs.web.sso.User user, TicketTypeStatus ticketTypeStatus, String operator) {
		String afterAssignType = ticketTypeStatus.getAfterAssignType();
		User assignUser = null;
		if (StringUtil.hasText(afterAssignType)) {
			if (DictionaryDataConstant.AFTER_ASSIGN_TYPE_DBA.equals(afterAssignType)) {
				assignUser = userService.getUserByLoginName("jun.lu");
			} else if (DictionaryDataConstant.AFTER_ASSIGN_TYPE_QALEADER.equals(afterAssignType)) {
				assignUser = userService.getUserByLoginName("jun.lu");
			} else if (DictionaryDataConstant.AFTER_ASSIGN_TYPE_OPS.equals(afterAssignType)) {
				assignUser = userService.getUserByLoginName("jun.lu");
			} else if (DictionaryDataConstant.AFTER_ASSIGN_TYPE_CLOSER.equals(afterAssignType)) {
				assignUser = userService.getUserByLoginName("closer");
			} else if (DictionaryDataConstant.AFTER_ASSIGN_TYPE_CREATOR.equals(afterAssignType)) {
				assignUser = userService.getUserByCode(ticket.getCreator());
			} else if (DictionaryDataConstant.AFTER_ASSIGN_TYPE_LEADER.equals(afterAssignType)) {
				assignUser = userService.getLeaderByUserCode(user.getCode());
			} else if (DictionaryDataConstant.AFTER_ASSIGN_TYPE_SUPEIOR.equals(afterAssignType)) {
				assignUser = userService.getSupeiorByUserCode(user.getCode());
			} else if (DictionaryDataConstant.AFTER_ASSIGN_TYPE_REPORTER.equals(afterAssignType)) {
				assignUser = userService.getUserByCode(ticket.getReporter());
			}
		} else if (StringUtil.hasText(operator) && !ticket.getOperator().equals(operator)) {
			assignUser = this.userService.getUserByCode(operator);
		}
		if (null != assignUser && !ticket.getOperator().equals(assignUser.getCode())) {
			TicketOperatorUpdate ticketOperatorUpdate = new TicketOperatorUpdate();
			ticketOperatorUpdate.setId(ticket.getId());
			ticketOperatorUpdate.setOperator(assignUser.getCode());
			ticketOperatorUpdate.setOperatorName(assignUser.getName());
			EntityUtil.initUpdateEntity(ticketOperatorUpdate);
			this.proxyDao.updateById(ticketOperatorUpdate);
			// 发送邮件给下一个经办人
			this.sendMail(ticket, assignUser, "ticketchangeOperator");
		}
	}

	private void autoAssign(String ticketId) {
		Ticket ticket = this.getTicketById(ticketId);
		List<UserAutoAssign> userAutoAssigns = userAutoAssignService.getValidUserAutoAssignFromUser(ticket.getOperator());
		if (userAutoAssigns.isEmpty())
			return;
		UserAutoAssign userAutoAssign = null;
		Date today = DateUtils.truncateTime(new Date());
		for (UserAutoAssign item : userAutoAssigns) {
			if (today.before(item.getDateFrom()))
				continue;
			if (today.after(item.getDateTo()))
				continue;
			else {
				userAutoAssign = item;
				break;
			}

		}
		if (null == userAutoAssign)
			return;
		systemUserHolder.holde();
		TicketUpdateOperatorVo ticketUpdateOperatorVo = new TicketUpdateOperatorVo();
		ticketUpdateOperatorVo.setTicketId(ticket.getId());
		ticketUpdateOperatorVo.setOperator(userAutoAssign.getAssignTo());
		ticketUpdateOperatorVo.setComments(ticket.getOperator() + "的自动转移设置启动工单转移到" + userAutoAssign.getAssignTo());
		changeOperatorTo(ticketUpdateOperatorVo);
		systemUserHolder.rest();
	}

	private void changeOperatorTo(TicketUpdateOperatorVo ticketUpdateOperatorVo) {
		User user = userService.getUserByCode(ticketUpdateOperatorVo.getOperator());
		Ticket ticket = this.getTicketById(ticketUpdateOperatorVo.getTicketId());
		com.eascs.web.sso.User currentUser = UserHolder.getUser();
		AssertInfoUtils.assertFalse(ticket.getOperator().equals(ticketUpdateOperatorVo.getOperator()), "本工单当前已经属于【{}】，无需再转移", user.getName());
		AssertInfoUtils.assertTrue(currentUser.isSuperAdministrator() || ticket.getOperator().equals(currentUser.getCode()) || currentUser.getCode().equals(ticket.getReporter()) || this.userService.isSupeior(ticket.getOperator(), currentUser.getCode()), "该工单只有经办人、经办人团队负责人、创建人、管理员才有权限转移");
		TicketOperatorUpdate ticketOperatorUpdate = new TicketOperatorUpdate();
		ticketOperatorUpdate.setId(ticket.getId());
		ticketOperatorUpdate.setOperator(ticketUpdateOperatorVo.getOperator());
		ticketOperatorUpdate.setOperatorName(user.getName());
		EntityUtil.initUpdateEntity(ticketOperatorUpdate);
		this.proxyDao.updateById(ticketOperatorUpdate);

		// insert comments
		insertComments(ticketUpdateOperatorVo.getTicketId(), ticketUpdateOperatorVo.getComments());
		// 发送邮件给下一个经办人
		this.sendMail(ticket, user, "ticketchangeOperator");
		ticketChangeLogService.insertTicketChangeLogWithObject(ticketUpdateOperatorVo.getTicketId(), "修改经办人", ticketUpdateOperatorVo);

		Ticket newTicket = this.getTicketById(ticketUpdateOperatorVo.getTicketId());
		// 工单转移时不需要做最后处理时间的更新
		ticketStatusTimeLogService.updateEndTimeLog(ticket.getId(), ticket.getStatus(), ticket.getOperator(), new Date());
		ticketStatusTimeLogService.insertStartTimeLog(newTicket.getId(), newTicket.getStatus(), newTicket.getOperator(), new Date());
	}

	/**
	 * @author 唐洪全注释：这一段是转移工单时修改经办人
	 * @param ticketUpdateOperatorVo
	 */
	@Transactional(transactionManager = "ticketTransactionManager")
	public void updateTicketOperator(TicketUpdateOperatorVo ticketUpdateOperatorVo) {
		this.changeOperatorTo(ticketUpdateOperatorVo);
		this.autoAssign(ticketUpdateOperatorVo.getTicketId());
	}

	/**
	 * @author 唐洪全 修改经办人发送邮件
	 * @param TicketFrom
	 * @param User
	 *            下一个经办人
	 */
	public void sendMail(Ticket ticket, User user, String customTypeCode) {

		Date nowDate = new Date();
		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String nowDateStr = df.format(nowDate);
		try {
			log.info("工单单号:[{}],发送人:[{}],收件人:[{}],发送时间:[{}],开始发送邮件入库", ticket.getId(), ticket.getOperatorName(), user.getName(), nowDateStr);
			// 如果转交人和接收人是同一个人，则不需要发邮件
			if (ticket.getOperator().equals(user.getName())) {
				return;
			}

			// 邮件标题
			String title = ticket.getTitle();
			/** 收件人 */
			String recipient = user.getEmail();
			if (StringUtil.isBlank(recipient)) {
				log.info("工单单号:[{}],发送人:[{}],收件人:[{}],发送时间:[{}],没有接收邮件地址不做邮件发送处理", ticket.getId(), ticket.getOperatorName(), user.getName(), nowDateStr);
				return;
			}
			/** 邮件内容 */
			// 生成邮件内容
			WFEmailContentVO contentVo = new WFEmailContentVO();
			contentVo.setTitle(title);
			contentVo.setLoanNo(ticket.getId());
			contentVo.setNowTime(nowDateStr);
			contentVo.setOperatorName(ticket.getOperatorName());
			contentVo.setReporterName(ticket.getReporterName());
			contentVo.setCreateTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(ticket.getCreateDate()));

			// 生成单号链接
			String url = String.format(GDDETAILURL, ticket.getId());
			contentVo.setLoanDetailUrl(url);
			String content = convertEmailContent(contentVo);
			if (!"".equals(content) && content != null) {
				mailNotifierComponent.notify(recipient, title, content, "TicketService");
				log.info("工单单号:[{}],发送人:[{}],收件人:[{}],发送时间:[{}],开始发送邮件入库成功", ticket.getId(), ticket.getOperatorName(), user.getName(), nowDateStr);
			} else {
				log.info("工单单号:[{}],发送人:[{}],收件人:[{}],发送时间:[{}],发送邮件内容不能为空", ticket.getId(), ticket.getOperatorName(), user.getName(), nowDateStr);
			}

		} catch (Exception e) {
			log.error("工单单号:[" + ticket.getId() + "],发送人:[" + ticket.getOperatorName() + "],收件人:[" + user.getName() + "],发送时间:[" + nowDateStr + "],邮件发送入库失败", e);
		}
	}

	private String convertEmailContent(WFEmailContentVO vo) {
		// 通过指定模板名获取FreeMarker模板实例
		try {
			Configuration configuration = new Configuration(Configuration.VERSION_2_3_24);
			configuration.setTemplateLoader(new ClassTemplateLoader(this.getClass(), "/email/"));
			Template template = configuration.getTemplate("wfEmail.ftl", "utf-8");
			StringWriter stringWriter = new StringWriter();
			// FreeMarker通过Map传递动态数据
			Map<String, Object> map = new HashMap<String, Object>();
			map.put("email", vo);
			template.process(map, stringWriter);
			return stringWriter.toString();
		} catch (Exception e) {
			log.error("生成邮件模板失败", e);
		}
		return null;
	}

	@Transactional(transactionManager = "ticketTransactionManager")
	public void insertComments(String ticketId, String comments) {
		// insert comments
		TicketComment ticketComment = new TicketComment();
		ticketComment.setTicketId(ticketId);
		ticketComment.setCommentContent(comments);
		ticketComment.setStatus(StatusConstant.STATUS_OK);
		EntityUtil.initInsertEntity(ticketComment);
		this.proxyDao.insert(ticketComment);
	}

	public List<Object[]> getTicketsByKey(String key) {
		return this.proxyDao.findObjectsList("select t.id,t.title,p.name from ticket t left join project p on p.id=t.project_id where t.id=? or t.title like ? limit 10", key, "%" + key + "%");
	}

	public List<Object[]> getTicketDataSource(String ticketId) {
		return this.proxyDao.findObjectsList("select id,title from ticket where id=?", ticketId);
	}

	@Transactional(transactionManager = "ticketTransactionManager")
	public TicketDueDateUpdate updateTicketDueDate(String ticketId) {
		Ticket ticket = this.getTicketById(ticketId);
		TicketStatus ticketStatus = ticketStatusService.getTicketStatusById(ticket.getStatus());

		TicketDueDate ticketDueDate = new TicketDueDate();
		ticketDueDate.setTicketId(ticket.getId());
		ticketDueDate.setTicketTypeCode(ticket.getTicketTypeCode());
		ticketDueDate.setTicketStatusType(ticketStatus.getTicketStatusType());
		ticketDueDate.setStatus(ticket.getStatus());
		ticketDueDate.setDueDate(ticket.getDueDate());
		ticketDueDate.setPublishProdTime(ticket.getPublishProdTime());
		ticketDueDate.setQaValidatedTime(ticket.getQaValidatedTime());
		ticketDueDate.setImplementationReviewTime(ticket.getImplementationReviewTime());
		ticketDueDate.setImplementationDoneTime(ticket.getImplementationDoneTime());
		ticketDueDate.setRequirementReviewedTime(ticket.getRequirementReviewedTime());
		ticketDueDate.setEndTime(ticket.getEndTime());
		ticketDueDate.setStartTime(ticket.getStartTime());

		if (StringUtil.hasText(ticket.getVersionTicketId())) {
			ticketDueDate.setVersionPublishProdTime(this.getTicketById(ticket.getVersionTicketId()).getPublishProdTime());
			checkDate(ticketDueDate);
		}

		Date dueDate = TicketDueDateHelper.buildTicketDueDate(ticketDueDate);
		TicketDueDateUpdate ticketDueDateUpdate = new TicketDueDateUpdate();
		ticketDueDateUpdate.setId(ticketId);
		ticketDueDateUpdate.setDueDate(dueDate);
		EntityUtil.initUpdateEntity(ticketDueDateUpdate);
		this.proxyDao.updateById(ticketDueDateUpdate);
		return ticketDueDateUpdate;
	}

	private void checkDate(TicketDueDate ticketDueDate) {
		if (null != ticketDueDate.getImplementationReviewTime()) {
			long oneDay = 1000 * 3600 * 24;
			long now = System.currentTimeMillis();
			long publishDays = (ticketDueDate.getVersionPublishProdTime().getTime() - now) / oneDay;
			if (publishDays > 6) {
				AssertInfoUtils.assertTrue(DateUtils.addDate(ticketDueDate.getImplementationReviewTime(), 2).getTime() - ticketDueDate.getVersionPublishProdTime().getTime() <= 0, "距离发布日至少还有6天请预留至少两天的测试时间！");
			} else if (publishDays > 4) {
				AssertInfoUtils.assertTrue(DateUtils.addDate(ticketDueDate.getImplementationReviewTime(), 1).getTime() - ticketDueDate.getVersionPublishProdTime().getTime() <= 0, "距离发布日至少还有4天请预留至少一天的测试时间！");
			}
			AssertInfoUtils.assertTrue(null == ticketDueDate.getRequirementReviewedTime() || ticketDueDate.getRequirementReviewedTime().getTime() - ticketDueDate.getVersionPublishProdTime().getTime() <= 0, "需求完成时间必须在版本发布之前完成");
			AssertInfoUtils.assertTrue(null == ticketDueDate.getRequirementReviewedTime() || ticketDueDate.getRequirementReviewedTime().getTime() - ticketDueDate.getImplementationReviewTime().getTime() <= 0, "需求完成时间必须在实现完成之前");
		}
	}

	public List<String> getAllTicketIds() {
		return this.proxyDao.findSimpleObjectList("select id from ticket order by create_date ", String.class);
	}

}
